package com.custom.action.condition;

import com.custom.action.util.DbUtil;
import com.custom.comm.enums.SqlExecTemplate;
import com.custom.comm.exceptions.CustomCheckException;
import com.custom.comm.utils.*;
import com.custom.comm.enums.DbSymbol;
import com.custom.comm.enums.SqlLike;
import com.custom.comm.enums.SqlOrderBy;
import com.custom.jdbc.configuration.GlobalDataHandler;

import java.lang.reflect.Array;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 条件适配处理对象
 * @param <T> 实体类型
 * @param <Param> 字段类型（字段类型为String是为字符串、lambda时为SFunction函数接口）
 * @param <Children> 继承该抽象类的子类类型
 * @author   Xiao-Bai
 * @since  2021/12/13 9:23
 **/
@SuppressWarnings("all")
public abstract class ConditionAssembly<T, Param, Children> extends ConditionWrapper<T>
        implements ConditionSplicer<Children>, QueryFunction<Children, T, Param> {


    /**
     * 适用（order by, is null, is not null, group by）
     */
    protected abstract Children adapter(boolean condition, DbSymbol dbSymbol, Param column);
    /**
     * 适用（exists, not exists）
     */
    protected abstract Children adapter(DbSymbol dbSymbol, boolean condition, String sqlColumn);
    /**
     * 适用（eq, ge, gt, le, lt, in, not in）
     */
    protected abstract Children adapter(DbSymbol dbSymbol, boolean condition, Param column, Object val);
    /**
     * 适用（between，not between, like, not like）
     */
    protected abstract Children adapter(DbSymbol dbSymbol, boolean condition, Param column, Object val1, Object val2);

    /**
     * 适用（exists, not exists）
     */
    protected abstract Children adapter(DbSymbol dbSymbol, boolean condition, Param column, String expression);

    /**
     * 子类的实例化
     */
    protected abstract Children getInstance();

    /**
     * 参数解析后，得到java属性字段对应的表字段名称
     */
    protected abstract String paramTransColumn(Param column);


    protected List<String> paramTransColumns(Param... columns) {
        return Arrays.stream(columns).map(this::paramTransColumn).collect(Collectors.toList());
    }


    /**
     * 拼接自定义的sql条件(该条件只支持拼接在where之后, group by之前)
     */
    public Children addCutsomizeSql(String customizeSql, Object... params) {
        if (JudgeUtil.isEmpty(customizeSql)) {
            return childrenClass;
        }
        this.addCustomizeSql(customizeSql);
        this.addParams(Arrays.stream(params).collect(Collectors.toList()));
        return childrenClass;
    }

    public Children addCutsomizeSql(boolean condition, String customizeSql, Object... params) {
        if (condition) {
            return addCutsomizeSql(customizeSql, params);
        }
        return childrenClass;
    }

    /**
     * 拼接最后的SQL条件(适用于having 之后，例如 limit 10)
     */
    public Children addSuffixSql(boolean condition, String suffixSql, Object... params) {
        if (condition) {
            return addSuffixSql(suffixSql, params);
        }
        return childrenClass;
    }

    public Children addSuffixSql(String suffixSql, Object... params) {
        if (StrUtils.isBlank(suffixSql)) {
            return childrenClass;
        }
        addSuffixSql(suffixSql);
        this.addParams(Arrays.stream(params).collect(Collectors.toList()));
        return childrenClass;
    }

    /**
    * 适配各种sql条件的拼接
    */
    protected void appendCondition(DbSymbol dbSymbol, boolean condition, String column, Object val1, Object val2, String expression) {

        // 若条件不满足，则返回
        if(!condition) {
            return;
        }
        column = this.checkedColumn(dbSymbol, column, expression);
        // sql最终条件组装
        this.handleFinalCondition(dbSymbol, column, val1, val2, expression);

        String lastCondition = getLastCondition();
        if(CustomUtil.isNotBlank(lastCondition)) {
            addCondition(lastCondition);
            setLastCondition(Constants.EMPTY);
        }
        setCurrAppendSymbol(Constants.AND);
    }

    private String checkedColumn(DbSymbol dbSymbol, String column, String expression) {
        AssertUtil.isTrue(CustomUtil.isNotBlank(column) || ALLOW_NOT_ALIAS.contains(dbSymbol),
                () -> new CustomCheckException("column cannot be empty"));
        if (CustomUtil.isNotBlank(expression)) {
            return expression;
        }
        if(!column.contains(Constants.POINT) && enableAlias) {
            column = DbUtil.fullSqlColumn(getTableSupport().alias(), column);
        }
        return column;
    }

    /**
     * sql最终条件组装
     */
    private void handleFinalCondition(DbSymbol dbSymbol, String column, Object val1, Object val2, String expression) {
        String appendSymbol = getCurrAppendSymbol();
        switch (dbSymbol) {
            case EQUALS:
            case NOT_EQUALS:
            case LESS_THAN:
            case GREATER_THAN:
            case LESS_THAN_EQUALS:
            case GREATER_THAN_EQUALS:
                
                setLastCondition(DbUtil.applyCondition(appendSymbol, column, dbSymbol));
                CustomUtil.addParams(getParamValues(), val1);
                break;

            case LIKE:
            case NOT_LIKE:
                setLastCondition(DbUtil.applyCondition(appendSymbol,
                        column, dbSymbol, SqlLike.sqlLikeConcat((SqlLike) val2)));
                addParams(val1);
                break;

            case IN:
            case NOT_IN:
                ConditionOnInsqlAssembly(dbSymbol, column, val1);
                break;

            case EXISTS:
            case NOT_EXISTS:
                setLastCondition(DbUtil.applyExistsCondition(appendSymbol, dbSymbol, expression));
                break;

            case BETWEEN:
            case NOT_BETWEEN:
                conditionOnSqlBetweenAssembly(dbSymbol, column, val1, val2);
                break;

            case IS_NULL:
            case IS_NOT_NULL:
                setLastCondition(DbUtil.applyIsNullCondition(appendSymbol, column, dbSymbol));
                break;

            case ORDER_BY:
            case ORDER_BY_ASC:
            case ORDER_BY_DESC:
                getOrderBy().add(column);
                break;

            case GROUP_BY:
                getGroupBy().add(column);
                break;

            case HAVING:
                getHaving().append(column);
                getHavingParams().addAll((List<Object>) val1);
                break;
        }
    }

    /**
     * between not between 的条件组装
     */
    private void conditionOnSqlBetweenAssembly(DbSymbol dbSymbol, String column, Object val1, Object val2) {
        AssertUtil.isTrue(CustomUtil.isBasicType(val1) && CustomUtil.isBasicType(val2),
                () -> new IllegalArgumentException("val1 or val2 can only be basic types"));

        AssertUtil.isTrue(JudgeUtil.isNotEmpty(val1) && JudgeUtil.isNotEmpty(val2),
                () -> new NullPointerException("At least one null value exists between val1 and val2"));
        setLastCondition(DbUtil.applyBetweenCondition(getCurrAppendSymbol(), column, dbSymbol));
        addParams(val1, val2);
    }

    /**
     * in 、not in的条件组装
     */
    private void ConditionOnInsqlAssembly(DbSymbol dbSymbol, String column, Object val) {
        StringJoiner symbol = new StringJoiner(Constants.SEPARATOR_COMMA_2);
        if (CustomUtil.isBasicType(val)) {
            addParams(val);

        } else if (val.getClass().isArray()) {
            int len = Array.getLength(val);
            for (int i = 0; i < len; i++) {
                symbol.add(Constants.QUEST);
                addParams(Array.get(val, i));
            }

        } else if (val instanceof Collection) {
            Collection<?> objects = (Collection<?>) val;
            addParams(objects);
            IntStream.range(0, objects.size()).forEach(x -> symbol.add(Constants.QUEST));
        }
        setLastCondition(DbUtil.applyInCondition(getCurrAppendSymbol(), column, dbSymbol, symbol.toString()));
    }

    /**
     * 拼接insql条件
     */
    protected void appendInSql(String column, DbSymbol dbSymbol, String condition, Object... params) {
        column = this.checkedColumn(dbSymbol, column, Constants.EMPTY);
        addCondition(DbUtil.applyInCondition(getCurrAppendSymbol(), column, dbSymbol, condition));
        if (params.length > 0) {
            addParams(params);
        }
    }

    /**
    * 排序字段整合
    */
    protected String appendOrderBy(String column, SqlOrderBy orderBy) {
        return orderBy.format(column);
    }



    /**
     * 拼接大条件
     * @param condition 是否拼接
     * @param spliceStatus true: 条件前缀为AND, false: 条件前缀为OR
     * @param wrapper 拼接的条件
     */
    protected Children spliceCondition(boolean condition, boolean spliceStatus, ConditionWrapper<T> wrapper) {
        if(condition && Objects.nonNull(wrapper)) {
            this.mergeConditionWrapper(spliceStatus, wrapper);
        }
        return childrenClass;
    }

    /**
     * sql查询函数执行方法
     */
    protected Children doSelectSqlFunc(Consumer<SelectFunc<T>> consumer) {
        SelectFunc<T> sqlFunc = new SelectFunc<>(getEntityClass());
        consumer.accept(sqlFunc);
        addSelectColumns(sqlFunc.getSqlFragments());
        return childrenClass;
    }


    /**
     * 合并新的条件构造器
     */
    protected void mergeConditionWrapper(boolean spliceStatus, ConditionWrapper<T> wrapper) {

        // 1. 合并查询列-select
        if (Objects.nonNull(wrapper.getSelectColumns())) {
            addSelectColumns(wrapper.getSelectColumns());
        }
        // 2. 合并添加一般条件-condition
        if (JudgeUtil.isNotEmpty(wrapper.getFinalConditional())) {
            mergeNormalCondition(spliceStatus, wrapper);
        }

        // 3. 合并分组-group by
        if (JudgeUtil.isNotEmpty(wrapper.getGroupBy())) {
            mergeGroupBy(wrapper);
        }

        // 4. 合并-having
        if (JudgeUtil.isNotEmpty(wrapper.getHaving())) {
            mergeHaving(wrapper);
        }

        // 5. 合并排序字段-orderBy
        if (JudgeUtil.isNotEmpty(wrapper.getOrderBy())) {
            mergeOrderBy(wrapper);
        }

    }

    /**
     * 合并条件
     * 合并前：name = 'aaa'
     * 合并后：name = 'aaa' and (age > 22)
     */
    private void mergeNormalCondition(boolean spliceStatus, ConditionWrapper<T> wrapper) {
        String maxCondition = DbUtil.appendMaxCondition(spliceStatus ? DbSymbol.AND : DbSymbol.OR, wrapper.getFinalConditional());
        addCondition(maxCondition);
        addParams(wrapper.getParamValues());
    }

    private void mergeOrderBy(ConditionWrapper<T> wrapper) {
        getOrderBy().merge(wrapper.getOrderBy());
    }

    private void mergeGroupBy(ConditionWrapper<T> wrapper) {
        getGroupBy().merge(wrapper.getOrderBy());
    }

    private void mergeHaving(ConditionWrapper<T> wrapper) {
        if (JudgeUtil.isEmpty(getHaving()) && JudgeUtil.isNotEmpty(wrapper.getHaving())) {
            getHaving().append(wrapper.getHaving());
        } else if (JudgeUtil.isNotEmpty(getHaving()) && JudgeUtil.isNotEmpty(wrapper.getHaving())) {
            getHaving().append(String.format(" AND %s ", wrapper.getHaving()));
        }
    }


    /**
     * 合并消费类型的条件
     */
    protected Children mergeConsumerCondition(boolean condition, boolean spliceStatus, Consumer<Children> consumer) {
        if (condition) {
            Children instance = getInstance();
            consumer.accept(instance);
            return spliceCondition(true, spliceStatus, (ConditionWrapper<T>) instance);
        }
        return childrenClass;
    }

    @Override
    public Children having(boolean condition, String havingSql, Object... params) {
        appendCondition(DbSymbol.HAVING, condition, havingSql, params, null, null);
        return childrenClass;
    }

    @Override
    public Children pageParams(Integer pageIndex, Integer pageSize) {
        AssertUtil.isTrue((Objects.nonNull(pageIndex) && Objects.nonNull(pageSize)),
                () -> new CustomCheckException("Missing paging parameter：pageIndex：%s, pageSize：%s", pageIndex, pageSize));
        setPageParams(pageIndex, pageSize);
        return childrenClass;
    }

    /**
     * 本次查询是否只查询主表
     */
    public Children onlyPrimary() {
        setPrimaryTable();
        return childrenClass;
    }

    protected final Children childrenClass = (Children) this;
    /**
     * 允许不包含别名的sql条件
     */
    private final static List<DbSymbol> ALLOW_NOT_ALIAS = Arrays.asList(DbSymbol.EXISTS, DbSymbol.NOT_EXISTS);


}
